package cn.virens.common.boot.filter.exception;

import cn.virens.common.spring.filter.AbstractPathMatcherFilter;
import cn.virens.common.util.exception.APIException;
import cn.virens.common.util.exception.ExceptionUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.alibaba.fastjson2.support.config.FastJsonConfig;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.dromara.hutool.core.collection.CollUtil;
import org.dromara.hutool.core.text.StrUtil;
import org.dromara.hutool.core.util.BooleanUtil;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.web.cors.CorsUtils;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;

public class SimpleHandlerExceptionFilter extends AbstractPathMatcherFilter {
    private Map<Class<? extends Exception>, String> exceptionMessages = new HashMap<>();

    private final SimpleHandlerExceptionProperties properties;
    private final FastJsonConfig mFastJsonConfig;

    public SimpleHandlerExceptionFilter(FastJsonConfig mFastJsonConfig, SimpleHandlerExceptionProperties properties) {
        this.mFastJsonConfig = mFastJsonConfig;
        this.properties = properties;
    }

    /**
     * 添加 异常消息
     *
     * @param exception 异常类型
     * @param message   异常消息
     */
    public void addExceptionMessage(Class<? extends Exception> exception, String message) {
        this.exceptionMessages.put(exception, message);
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException {
        try {
            // 如果是跨域请求,先处理跨域响应头
            if (CorsUtils.isCorsRequest(request)) {
                beforeHandleCors(request, response);
            }

            // 如果不是跨域前置头,放行请求处理
            if (!CorsUtils.isPreFlightRequest(request)) {
                filterChain.doFilter(request, response);
            }
        } catch (Throwable ex) {
            resolveException(request, response, ex(ex, null));
        }
    }

    @Override
    protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
        return CollUtil.isNotEmpty(patterns) && super.shouldNotFilter(request);
    }

    /**
     * 异常处理路由：判断是否需要打印异常信息
     */
    protected void resolveException(HttpServletRequest request, HttpServletResponse response, Throwable ex) throws IOException {
        if (ex != null && logger.isErrorEnabled() && BooleanUtil.isTrue(properties.getExceptionFull())) {
            logger.error(ex.getMessage(), ex);
        } else if (ex != null && logger.isErrorEnabled()) {
            logger.error(ex.getMessage());
        }

        try (ByteArrayOutputStream outnew = new ByteArrayOutputStream()) {
            // 构建错误信息内容对象
            JSONObject object = new JSONObject();
            object.put("code", getExceptionCode(request, ex));
            object.put("message", getExceptionMessage(request, ex));

            // 重置响应流 & 设置缓存无效
            response.resetBuffer();
            response.addDateHeader("Expires", 1L);
            response.addHeader("Pragma", "no-cache");
            response.addHeader("Cache-Control", "no-cache, no-store, max-age=0");

            //  设置响应类型为json,并读取对应的长度
            response.setContentLength(writeJSON(outnew, object));
            response.setContentType(MediaType.APPLICATION_JSON_VALUE);
            response.setCharacterEncoding(Charset.defaultCharset().name());

            // 获取响应输出流,并将json内容输出到浏览器
            try (ServletOutputStream out = response.getOutputStream()) {
                outnew.writeTo(out);
                outnew.close();
                out.flush();
            }
        }
    }

    private int writeJSON(ByteArrayOutputStream out, JSONObject object) throws APIException {
        if (mFastJsonConfig != null && mFastJsonConfig.getWriterFilters() != null) {
            return JSON.writeTo(out, object, mFastJsonConfig.getWriterFilters());
        } else {
            return JSON.writeTo(out, object);
        }
    }

    /**
     * 根据请求中的跨域头信息设置响应所需的跨域头
     */
    private void beforeHandleCors(HttpServletRequest request, HttpServletResponse response) throws APIException {
        var expose_headers = headers(request, HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS, properties.getExposeHeaders());
        var allow_headers = headers(request, HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS, properties.getAllowHeaders());
        var allow_methods = headers(request, HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, properties.getAllowMethods());
        var allow_origin = headers(request, HttpHeaders.ORIGIN, properties.getAllowOrigins());

        response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, properties.getCredentials());
        response.setHeader(HttpHeaders.ACCESS_CONTROL_MAX_AGE, properties.getMaxAge());

        response.setHeader(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, expose_headers);
        response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, allow_headers);
        response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, allow_methods);
        response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, allow_origin);
    }

    private static String headers(HttpServletRequest request, String key, String defaultStr) {
        return StrUtil.defaultIfEmpty(request.getHeader(key), defaultStr);
    }

    /**
     * 根据异常获取对应的错误编号
     */
    protected String getExceptionCode(HttpServletRequest req, Throwable ex) {
        return ExceptionUtil.getCode(ex);
    }

    /**
     * 根据异常获取对应的错误提示
     */
    protected String getExceptionMessage(HttpServletRequest req, Throwable ex) {
        for (Class<?> calzz : exceptionMessages.keySet()) {
            if (ex != null && isAssignableException(calzz, ex)) {
                return exceptionMessages.get(calzz);//
            }
        }

        return ExceptionUtil.getMessage(ex);
    }

    /**
     * 异常过滤,剔除异常包装类
     */
    private static final Throwable ex(Throwable ex, Throwable p) throws APIException {
        if (ex instanceof ServletException) {
            return ex(ex.getCause(), ex);
        } else if (ex != null) {
            return ex;
        } else {
            return p;
        }
    }

    /**
     * 判断异常是否属于指定类型
     */
    private static boolean isAssignableException(Class<?> c, Throwable ex) {
        return c != null && ex != null && c.isAssignableFrom(ex.getClass());
    }

    /**
     * 将Spring默认的异常处理释放到拦截器中进行，防止提前对状态码异常进行处理
     */
    public static class EmptyExceptionResolver implements HandlerExceptionResolver {

        @Override
        public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
            return null;
        }
    }
}
